﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Collections.Specialized;
using System.Linq;

namespace Odyssey.ComponentModel
{
    /// <summary>
    /// CollectionView which supports grouping to be used together with <see cref="OdcListBox"/>.
    /// </summary>
    public class JumpListCollectionView : ICollectionView
    {
        private IEnumerable source;
        private Dictionary<object, JumpListGroup> jumpGroups = new Dictionary<object, JumpListGroup>();
        private ObservableCollection<object> groups = new ObservableCollection<object>();
        private ReadOnlyObservableCollection<object> readonlyGroups;
        private ObservableCollection<GroupDescription> groupDescriptions = new ObservableCollection<GroupDescription>();
        private ObservableCollection<object> collection = new ObservableCollection<object>();
        private int currentPosition = -1;

        public JumpListCollectionView(IEnumerable source)
            : base()
        {
            Culture = CultureInfo.CurrentCulture;
            readonlyGroups = new ReadOnlyObservableCollection<object>(groups);
            groupDescriptions.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(OnGroupDescriptionChanged);
            collection.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
            this.SourceCollection = source;
        }

        protected virtual void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null) CollectionChanged(this, e);
        }

        /// <summary>
        /// Gets whether the collection contains groups.
        /// </summary>
        public bool IsGrouped
        {
            get { return groupDescriptions.Count > 0; }
        }

        /// <summary>
        /// Gets the first and only GroupDescription, otherwise null.
        /// </summary>
        protected GroupDescription GroupDescription
        {
            get { return groupDescriptions.Count == 0 ? null : groupDescriptions[0]; }
        }

        private void OnGroupDescriptionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (groupDescriptions.Count > 1) throw new OverflowException("Number of GroupDescriptions exceeds maximum of 1.");
        }

        #region ICollectionView Members

        public virtual bool CanFilter { get { return true; } }

        public virtual bool CanGroup { get { return true; } }

        public virtual bool CanSort { get { return false; } }

        public bool Contains(object item)
        {
            return collection.Contains(item);
        }

        /// <summary>
        ///     Gets or sets the cultural information for any operations of the view that
        ///     may differ by culture, such as sorting.
        /// </summary>       
        public System.Globalization.CultureInfo Culture { get; set; }

        public event EventHandler CurrentChanged;

        public event CurrentChangingEventHandler CurrentChanging;

        public object CurrentItem
        {
            get { return currentPosition >= 0 ? collection[currentPosition] : null; }
        }

        public int CurrentPosition
        {
            get { return currentPosition; }
        }

        private class InternalDeferRefesh : IDisposable
        {
            private JumpListCollectionView collectionView;

            public InternalDeferRefesh(JumpListCollectionView view)
                : base()
            {
                this.collectionView = view;
            }

            public void Dispose()
            {
                collectionView.collection.CollectionChanged += collectionView.OnCollectionChanged;
                collectionView.OnCollectionChanged(collectionView.collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                GC.SuppressFinalize(this);
            }

            ~InternalDeferRefesh()
            {
                Dispose();
            }

        }

        /// <summary>
        /// Enters a defer cycle that you can use to merge changes to the view and delay automatic refresh.
        ///     The typical usage is to create a using scope with an implementation of this
        ///     method and then include multiple view-changing calls within the scope. The
        ///     implementation should delay automatic refresh until after the using scope
        ///     exits.
        /// </summary>
        /// <returns></returns>
        public IDisposable DeferRefresh()
        {
            collection.CollectionChanged -= OnCollectionChanged;
            return new InternalDeferRefesh(this);
        }


        /// <summary>
        /// Gets or sets the filter method to apply against the collection.
        /// </summary>
        public Predicate<object> Filter { get; set; }

        public virtual ObservableCollection<GroupDescription> GroupDescriptions
        {
            get { return groupDescriptions; }
        }

        public virtual ReadOnlyObservableCollection<object> Groups
        {
            get { return readonlyGroups; }
        }


        /// <summary>
        /// Gets a value that indicates whether the System.ComponentModel.ICollectionView.CurrentItem
        ///  of the view is beyond the end of the collection.
        /// </summary>
        public bool IsCurrentAfterLast
        {
            get { return currentPosition >= collection.Count; }
        }

        /// <summary>
        ///   Gets a value that indicates whether the System.ComponentModel.ICollectionView.CurrentItem
        ///   of the view is beyond the start of the collection.        
        /// </summary>
        public bool IsCurrentBeforeFirst
        {
            get { return currentPosition < 0; }
        }

        public bool IsEmpty
        {
            get { return collection.Count == 0; }
        }

        public bool MoveCurrentTo(object item)
        {
            int index = collection.IndexOf(item);
            return MoveCurrentToPosition(index);
        }

        public bool MoveCurrentToFirst()
        {
            if (collection.Count == 0) return false;
            return MoveCurrentToPosition(0);
        }

        public bool MoveCurrentToLast()
        {
            if (collection.Count == 0) return false;
            return MoveCurrentToPosition(collection.Count - 1);
        }

        public bool MoveCurrentToNext()
        {
            if ((collection.Count - 1) >= currentPosition) return false;
            return MoveCurrentToPosition(collection.Count + 1);
        }

        public bool MoveCurrentToPosition(int position)
        {
            if (position < 0 || position >= collection.Count) position = -1;
            if (this.currentPosition != position)
            {
                return OnCurrentChanging(position);
            }
            else return false;
        }

        public bool MoveCurrentToPrevious()
        {
            int pos = currentPosition - 1;
            if (pos < 0) return false;
            return MoveCurrentToPosition(pos);
        }

        /// <summary>
        /// Recreates the view.
        /// </summary>
        public virtual void Refresh()
        {
            this.currentPosition = -1;
            jumpGroups.Clear();
            groups.Clear();
            collection.Clear();
            RepopulateDefaultGroups();
            if (source == null) return;

            if (!IsGrouped)
            {
                RefreshUngrouped();
            }
            else
            {
                RefreshGrouped();
            }
        }

        private void RefreshUngrouped()
        {
            Predicate<object> filter = Filter;
            IEnumerable items = filter == null ? source : source.Cast<object>().Where(o => filter(o));

            foreach (object item in items)
            {
                collection.Add(item);
            }
        }

        private void RefreshGrouped()
        {
            CultureInfo culture = Culture;
            GroupDescription gd = groupDescriptions[0];
            Predicate<object> filter = Filter;
            IEnumerable items = filter == null ? source : source.Cast<object>().Where(o => filter(o));
            foreach (object item in source)
            {
                object name = gd.GroupNameFromItem(item, 0, culture);
                JumpListGroup group = GetGroupFromName(name);
                group.Items.Add(item);
            }

            foreach (JumpListGroup group in groups)
            {
                if (group == null || group.ItemCount == 0) continue;
                collection.Add(group);
                foreach (object item in group.Items)
                {
                    collection.Add(item);
                }
            }
        }

        /// <summary>
        /// Gets an existing group or create a new group.
        /// </summary>
        /// <param name="name">Name of the group.</param>
        private JumpListGroup GetGroupFromName(object name)
        {
            JumpListGroup group;
            if (!jumpGroups.TryGetValue(name, out group))
            {
                group = new JumpListGroup { Name = name };
                groups.Add(group);
                jumpGroups.Add(name, group);
            }
            return group;
        }

        private void RepopulateDefaultGroups()
        {
            foreach (GroupDescription gd in groupDescriptions)
            {
                foreach (object groupName in gd.GroupNames)
                {
                    JumpListGroup jlg = new JumpListGroup { Name = groupName };
                    groups.Add(jlg);
                    jumpGroups.Add(groupName, jlg);
                }
            }
        }

        /// <summary>
        /// Currently not supported.
        /// </summary>
        public virtual SortDescriptionCollection SortDescriptions
        {
            get { return null; }
        }

        /// <summary>
        /// Gets the collection of the source.
        /// </summary>
        public IEnumerable SourceCollection
        {
            get { return source; }
            set
            {
                if (source != value)
                {
                    IEnumerable oldValue = source;
                    source = value;
                    OnSourceChanged(oldValue, source);
                }
            }
        }

        private void OnSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
            INotifyCollectionChanged oldNCC = oldValue as INotifyCollectionChanged;

            if (oldNCC != null)
            {
                oldNCC.CollectionChanged -= OnSourceCollectionChanged;
            }

            Refresh();

            INotifyCollectionChanged newNCC = newValue as INotifyCollectionChanged;
            if (newNCC != null)
            {
                newNCC.CollectionChanged += new NotifyCollectionChangedEventHandler(OnSourceCollectionChanged);
            }
        }

        void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Reset:
                    Refresh();
                    break;

                case NotifyCollectionChangedAction.Add:
                    foreach (object item in e.NewItems)
                    {
                        OnItemAdded(item);
                    }
                    break;

                case NotifyCollectionChangedAction.Remove:
                    foreach (object item in e.OldItems)
                    {
                        OnItemRemoved(item);
                    }
                    break;

                default:
                    throw new NotSupportedException();
            }
        }

        /// <summary>
        /// when an item is removed from source, the group must be updated and necassarily removed from the output collection.
        /// </summary>
        /// <param name="item"></param>
        private void OnItemRemoved(object item)
        {
            collection.Remove(item);

            GroupDescription gd = GroupDescription;
            if (gd != null)
            {
                object name = gd.GroupNameFromItem(item, 0, Culture);
                JumpListGroup group = GetGroupFromName(name);
                group.Items.Remove(item);
                if (group.ItemCount == 0)
                {
                    collection.Remove(item);
                }
            }
        }

        /// <summary>
        /// When an item is added to the source, the group and the index in the group must be determined where to insert, or if a new group must be created.
        /// </summary>
        private void OnItemAdded(object item)
        {
            var filter = Filter;
            if (filter != null && !filter(item)) return;

            GroupDescription gd = GroupDescription;
            if (gd != null)
            {
                object name = gd.GroupNameFromItem(item, 0, Culture);
                JumpListGroup group = GetGroupFromName(name);
                int groupIndex = groups.IndexOf(group);
                int index;
                if (group.ItemCount == 0)
                {
                    if (groupIndex > 0)
                    {
                        JumpListGroup parent;
                        do
                        {
                            parent = groups[groupIndex - 1] as JumpListGroup;
                            groupIndex--;
                        } while (groupIndex > 0 && parent.ItemCount == 0);
                        index = collection.IndexOf(parent) + parent.ItemCount + 1;
                    }
                    else index = 0;
                    collection.Insert(index, group);
                }
                else index = collection.IndexOf(group);
                group.Items.Add(item);
                collection.Insert(index + group.ItemCount, item);
            }
            else
            {
                IList list = source as IList;
                if (list != null)
                {
                    collection.Insert(list.IndexOf(item), item);
                }
                else
                {
                    collection.Add(item);
                }
            }
        }

        #endregion

        #region IEnumerable Members

        public System.Collections.IEnumerator GetEnumerator()
        {
            return collection.GetEnumerator();
        }

        #endregion

        #region INotifyCollectionChanged Members

        public event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion


        protected virtual bool OnCurrentChanging(int newPosition)
        {
            if (CurrentChanging != null)
            {
                CurrentChangingEventArgs e = new CurrentChangingEventArgs(true);
                CurrentChanging(this, e);
                if (e.Cancel) return false;
            }
            currentPosition = newPosition;
            if (CurrentChanged != null)
            {
                CurrentChanged(this, EventArgs.Empty);
            }
            return true;
        }
    }
}
